[Chapter Nineteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Nineteen
- 19.4.2 - The UCR Standard Library Processes
Package
- 19.4.3 - Problems with Multitasking
19.4.2 The UCR Standard Library Processes Package
The UCR Standard Library provides six routines to let you manage threads.
These routines include prcsinit
, prcsquit
, fork
,
die
, kill
, and yield
. These functions
let you initialize and shut down the threads system, start new processes,
terminate processes, and voluntarily pass the CPU off to another process.
The prcsinit
and prcsquit
functions let you initialize
and shutdown the system. The prcsinit
call prepares the threads
package. You must call this routine before executing any of the other five
process routines. The prcsquit
function shuts down the threads
system in preparation for program termination. Prcsinit
patches
into the timer interrupt (interrupt 8). Prcsquit
restores the
interrupt 8 vector. It is very important that you call prcsquit
before your program returns to DOS. Failure to do so will leave the int
8 vector pointing off into memory which may cause the system to crash when
DOS loads the next program. Your program must patch the break and critical
error exception vectors to ensure that you call prcsquit
in
the event of abnormal program termination. Failure to do so may crash the
system if the user terminates the program with ctrl-break or an abort on
an I/O error. Prcsinit
and prcsquit
do not require
any parameters, nor do they return any values.
The fork
call spawns a new process. On entry, es:di
must point at a pcb for the new process. The regss
and regsp
fields of the pcb
must contain the address of the top of the
stack area for this new process. The fork
call fills in the
other fields of the pcb
(including cs:ip)/
For each call you make to fork
, the fork
routine
returns twice, once for each thread of execution. The parent process typically
returns first, but this is not certain; the child process is usually the
second return from the fork
call. To differentiate the two
calls, fork
returns two process identifiers (PIDs) in the ax
and bx
registers. For the parent process, fork
returns with ax
containing zero and bx
containing
the PID of the child process. For the child process, fork
returns
with ax
containing the child's PID and bx
containing
zero. Note that both threads return and continuing executing the same code
after the call to fork
. If you want the child and parent processes
to take separate paths, you would execute code like the following:
lesi NewPCB ;Assume regss/regsp are initialized.
fork
test ax, ax ;Parent PID is zero at this point.
je ParentProcess ;Go elsewhere if parent process.
; Child process continues execution here
The parent process should save the child's PID. You can use the PID to terminate
a process at some later time.
It is important to repeat that you must initialize the regss
and regsp
fields in the pcb
before calling fork
.
You must allocate storage for a stack (dynamically or statically) and point
ss:sp
at the last word of this stack area. Once you call fork
,
the process package uses whatever value that happens to be in the regss
and regsp
fields. If you have not initialized these values,
they will probably contain zero and when the process starts it will wipe
out the data at address 0:FFFE. This may crash the system at one point or
another.
The die
call kills the current process. If there are multiple
processes running, this call transfers control to some other processes waiting
to run. If the current process is the only process on the system's run queue,
then this call will crash the system.
The kill
call lets one process terminate another. Typically,
a parent process will use this call to terminate a child process. To kill
a process, simply load the ax
register with the PID of the
process you want to terminate and then call kill
. If a process
supplies its own PID to the kill
function, the process terminates
itself (that is, this is equivalent to a die
call). If there
is only one process in the run queue and that process kills itself, the
system will crash.
The last multitasking management routine in the process package is the yield
call. Yield
voluntarily gives up the CPU. This is a direct
call to the dispatcher, that will switch to another task in the run queue.
Control returns after the yield
call when the next time slice
is given to this process. If the current process is the only one in the
queue, yield
immediately returns. You would normally use the
yield
call to free up the CPU between long I/O operations (like
waiting for a keypress). This would allow other tasks to get maximum use
of the CPU while your process is just spinning in a loop waiting for some
I/O operation to complete.
The Standard Library multitasking routines only work with the 16 bit register
set of the 80x86 family. Like the coroutine package, you will need to modify
the pcb and the dispatcher code if you want to support the 32 bit register
set of the 80386 and later processors. This task is relatively simple and
the code is quite similar to that appearing in the section on coroutines;
so there is no need to present the solution here.
19.4.3 Problems with Multitasking
When threads share code and data certain problems can develop. First
of all, reentrancy becomes a problem. You cannot call a non-reentrant routine
(like DOS) from two separate threads if there is ever the possibility that
the non-reentrant code could be interrupted and control transferred to a
second thread that reenters the same routine. Reentrancy is not the only
problem, however. It is quite possible to design two routines that access
shared variables and those routines misbehave depending on where the interrupts
occur in the code sequence. We will explore these problems in the section
on synchronization (see "Synchronization"
on page 1129), just be aware, for now, that these problems exist.
Note that simply turning off the interrupts (with cli
) may
not solve the reentrancy problem. Consider the following code:
cli ;Prevent reentrancy.
mov ah, 3Eh ;DOS close call.
mov bx, Handle
int 21h
sti ;Turn interrupts back on.
This code will not prevent DOS from being reentered because DOS (and BIOS)
turn the interrupts back on! There is a solution to this problem, but it's
not by using cli
and sti
.
- 19.4.2 - The UCR Standard
Library Processes Package
- 19.4.3 - Problems with Multitasking
Art of Assembly: Chapter Nineteen - 29 SEP 1996
[Chapter Nineteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]